/**
 * 
 */
package epsofts.Neutron.mesures;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import epsofts.Neutron.EvalueurNbLibertes;
import epsofts.Neutron.IEvalueur;
import epsofts.Neutron.IJoueur;
import epsofts.Neutron.INeutronUI;
import epsofts.Neutron.Jeu;
import epsofts.Neutron.JoueurHumain;
import epsofts.Neutron.JoueurMiniMaxAB;
import epsofts.Neutron.JoueurUCT;
import epsofts.Neutron.Plateau;
import epsofts.Neutron.eCase;
import epsofts.Neutron.Jeu.DrawGameException;


/**
 * @author Pierre ESCALLIER
 * 
 */
public class TestMain
{
	final static String			PARTIE_PREFIX			= "partie_";

	final static String			SESSION_PREFIX			= "session_";

	final static short			PROFONDEUR_MAX			= 4;

	final static short			COUPS_ALEATOIRES		= 1;
	
	final static int			TIMEOUT_UCT				= 30000;

	final static IEvalueur		EVALUEUR				= new EvalueurNbLibertes();

	static boolean				tournoi_ouvert			= false;
	
	static String				ai_rouge = null;
	
	static String				ai_bleu = null;

	static IEvalueur			evalueur_rouge			= EVALUEUR;

	static IEvalueur			evalueur_bleu			= EVALUEUR;

	static short				profondeur_max_rouge	= PROFONDEUR_MAX;

	static short				profondeur_max_bleu		= PROFONDEUR_MAX;

	static short				coups_aleatoires_rouge	= COUPS_ALEATOIRES;

	static short				coups_aleatoires_bleu	= COUPS_ALEATOIRES;
	
	static int					timeout_uct_rouge			= TIMEOUT_UCT;
	
	static int 					timeout_uct_bleu			= TIMEOUT_UCT;

	static int					nb_manches				= 1;

	static String				REPERTOIRE				= System.getProperty("user.dir");

	static File					repertoire_session		= new File(REPERTOIRE);

	static eCase				VUE						= eCase.ROUGE;

	static HashSet<String>		GO						= new HashSet<String>();

	final static BufferedReader	FromKeyboard			= new BufferedReader(new InputStreamReader(System.in));

	protected static File getRepertoireSession()
	{
		int id_session = 1;
		File fic = null;
		do
		{
			fic = new File(String.format("%s" + File.separator + "%s%d" + File.separator + "", REPERTOIRE, SESSION_PREFIX, id_session));
			++id_session;
		} while (fic.exists());

		return fic;
	}

	private static IJoueur choix_joueur(final String sNom, final String sAICode, final short iProfondeur, final IEvalueur evalueur, final short iNbCoupsAleatoires, int timeOut)
	{
		char choix = 0;

		if ( !GO.contains("CPU_" + sNom.toUpperCase()))
		{
			System.out.println("Joueur " + sNom + " [H]umain, cpu [A]lphaBeta, cpu [U]CT : ");

			do
			{
				try
				{
					choix = (char) FromKeyboard.readLine().charAt(0);
				}
				catch (IOException e)
				{
					System.out.println("Erreur de saisie");
					choix = 0;
				}
			} while ((choix != 'H') && (choix != 'h') && (choix != 'A') && (choix != 'a') && (choix != 'U') && (choix != 'u'));
		}
		else
		{
			choix = 'A';
			if (sAICode != null)
			{
				if ("MinMax".compareToIgnoreCase(sAICode) == 0 || "MiniMax".compareToIgnoreCase(sAICode) == 0 || "MinMaxAB".compareToIgnoreCase(sAICode) == 0 || "MiniMaxAB".compareToIgnoreCase(sAICode) == 0)
				{
					choix = 'A';
				}
				else if ("UCT".compareToIgnoreCase(sAICode) == 0)
				{
					choix = 'U';
				}
			}
		}

		if ((choix == 'H') || (choix == 'h'))
		{
			return new JoueurHumain();
		}
		else if ((choix == 'A') || (choix == 'a'))
		{
			System.out.println(String.format("JoueurMiniMaxAB(%d, %s, %d)", iProfondeur, evalueur.getClass().toString(), iNbCoupsAleatoires));
			return new JoueurMiniMaxAB(iProfondeur, evalueur, iNbCoupsAleatoires);
		}
		else if ((choix == 'U') || (choix == 'u'))
		{
			
			System.out.println(String.format("JoueurUCT(%d, %s, %d)", timeOut, evalueur, iNbCoupsAleatoires));
			return new JoueurUCT(timeOut, evalueur, iNbCoupsAleatoires);
		}
		else
		{
			return choix_joueur(sNom, sAICode, iProfondeur, evalueur, iNbCoupsAleatoires, timeOut);
		}
	}

	public static void testJarListing()
	{
		URL url = null;
		Object content = null;

		try
		{
			url = new URL("file:/media/data/Code/Java_Workspace/JNeutron/bin/JNeutron.jar");
			File f = new File(url.getFile());
			String i_package = "Neutron/GUI";

			JarFile jar = new JarFile(f);

			System.out.println(jar);

			JarEntry packageEntry = jar.getJarEntry(i_package);
			if (packageEntry == null) return;

			Enumeration<JarEntry> entries = jar.entries();
			while (entries.hasMoreElements())
			{
				JarEntry entry = entries.nextElement();

				String s = entry.getName();
				System.out.print(entry);
				if ( !entry.isDirectory() && s.startsWith(packageEntry.getName()) && s.endsWith(".class") && s.indexOf('$') < 0)
				{
					// accept
					System.out.print("\tOK");
					s = s.substring(0, s.length() - 6);
					s = s.substring(packageEntry.getName().length() - i_package.length(), s.length()).replaceAll("/", ".").replaceAll("\\\\", ".");
				}
				System.out.println();
			}

		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		System.out.println("[" + url + "]");
		System.out.println(content);
	}

	public static void main(String[] args)
	{
		//testJarListing();

		for (int i = 0; (i < args.length); ++i)
		{

			if (args[i].toUpperCase().startsWith("-MAX_DEPTH"))
			{
				try
				{
					if (args[i].substring(11).compareToIgnoreCase("ROUGE") == 0)
					{
						profondeur_max_rouge = Short.parseShort(args[i + 1]);
						GO.add("MAX_DEPTH_ROUGE");
						GO.add("CPU_ROUGE");
					}
					if (args[i].substring(11).compareToIgnoreCase("BLEU") == 0)
					{
						profondeur_max_bleu = Short.parseShort(args[i + 1]);
						GO.add("MAX_DEPTH_BLEU");
						GO.add("CPU_BLEU");
					}
				}
				catch (Exception e)
				{
					System.err.println("MAX_DEPTH : valeur incorrecte.");
					return;
				}
			}

			if (args[i].toUpperCase().startsWith("-AI") || args[i].toUpperCase().startsWith("-IA"))
			{
				String ai = args[i+1];

				if (args[i].substring(4).compareToIgnoreCase("ROUGE") == 0)
				{
					ai_rouge = ai;
					GO.add("AI_ROUGE");
					GO.add("CPU_ROUGE");
				}
				if (args[i].substring(4).compareToIgnoreCase("BLEU") == 0)
				{
					ai_bleu = ai;
					GO.add("AI_BLEU");
					GO.add("CPU_BLEU");
				}
			}
			
			if (args[i].toUpperCase().startsWith("-BRAIN"))
			{
				Class<?> classe = null;
				try
				{
					classe = Class.forName(args[i + 1]);
				}
				catch (ClassNotFoundException e)
				{
					System.err.println("BRAIN : classe introuvable");
					return;
				}
				if (IEvalueur.class.isAssignableFrom(classe) == false)
				{
					System.err.println("BRAIN : classe incorrecte");
					return;
				}

				try
				{
					if (args[i].substring(7).compareToIgnoreCase("ROUGE") == 0)
					{
						evalueur_rouge = (IEvalueur) classe.newInstance();
						GO.add("BRAIN_ROUGE");
						//GO.add("CPU_ROUGE");
					}
					if (args[i].substring(7).compareToIgnoreCase("BLEU") == 0)
					{
						evalueur_bleu = (IEvalueur) classe.newInstance();
						GO.add("BRAIN_BLEU");
						//GO.add("CPU_BLEU");
					}
				}
				catch (InstantiationException e)
				{
					System.err.println("BRAIN : erreur d'instanciation");
					return;
				}
				catch (IllegalAccessException e)
				{
					System.err.println("BRAIN : classe incorrecte");
					return;
				}
			}

			if (args[i].toUpperCase().startsWith("-RANDOM_MOVES"))
			{
				if (args[i].substring(14).compareToIgnoreCase("ROUGE") == 0)
				{
					coups_aleatoires_rouge = Short.parseShort(args[i + 1]);
					GO.add("RANDOM_MOVES_ROUGE");
					GO.add("CPU_ROUGE");
				}
				if (args[i].substring(14).compareToIgnoreCase("BLEU") == 0)
				{
					coups_aleatoires_bleu = Short.parseShort(args[i + 1]);
					GO.add("RANDOM_MOVES_BLEU");
					GO.add("CPU_BLEU");
				}
			}

			if (args[i].toUpperCase().startsWith("-GAMES"))
			{
				nb_manches = Integer.parseInt(args[i + 1]);
				GO.add("GAMES");
			}

			if (args[i].toUpperCase().startsWith("-DIR"))
			{
				REPERTOIRE = args[i + 1];
				GO.add("DIR");
			}

			if (args[i].toUpperCase().startsWith("-VUE"))
			{
				if (args[i].substring(5).compareToIgnoreCase("ROUGE") == 0)
				{
					VUE = eCase.ROUGE;
				}
				if (args[i].substring(5).compareToIgnoreCase("BLEU") == 0)
				{
					VUE = eCase.BLEUE;
				}
			}
			
			if (args[i].toUpperCase().startsWith("-TIMEOUT"))
			{
				if (args[i].substring(9).compareToIgnoreCase("ROUGE") == 0)
				{
					timeout_uct_rouge = Short.parseShort(args[i + 1]);
					GO.add("TIMEOUT_ROUGE");
					GO.add("CPU_ROUGE");
				}
				if (args[i].substring(9).compareToIgnoreCase("BLEU") == 0)
				{
					timeout_uct_bleu = Short.parseShort(args[i + 1]);
					GO.add("TIMEOUT_BLEU");
					GO.add("CPU_BLEU");
				}
			}

			if (args[i].toUpperCase().startsWith("LISTER_COUPS"))
			{
				GO.add("LISTER_COUPS");
			}

			if (args[i].toUpperCase().startsWith("HELP"))
			{
				System.out.print("Syntaxe : Neutron [-options] [lister_coups]\r\n");
				System.out.println("Avec pour options :");
				System.out.println("\t-RANDOM_MOVES_[ROUGE|BLEU] int : Nombre de coups aléatoires en début de partie (bleu ou rouge).");
				System.out.println("\t-AI_[ROUGE|BLEU] [MinMax|UCT] : Choix de l'algorithme d'IA.");
				System.out.println("\t-MAX_DEPTH_[ROUGE|BLEU] int : Profondeur max du joueur CPU (IA MiniMax).");
				System.out.println("\t-TIMEOUT_[ROUGE|BLEU] : TimeOut (IA UCT) en ms.");
				System.out.println("\t-BRAIN_[ROUGE|BLEU] NomClasse : Nom de la classe implémentant IEvalueur (pour le joueur bleu ou rouge).");
				
				System.out.println("\t-GAMES int : Nombre de parties.");
				System.out.println("\t-DIR repertoire : Répertoire de travail.");
				System.out.println("\t-VUE_[ROUGE|BLEU] : Vue à laquelle convertir l'historique des parties (pour exploitation des mesures).");
				
				System.out.println("");
				System.out.println("Commandes :");
				System.out.println("LISTER_COUPS : Extrait les coups des parties (ne lance pas de tournoi).");
				System.out.println("\t-LIMITE int : Score minimum à conserver lors de l'extraction des mesures.");
				System.out.println("\t-FUSION : Analyse toutes les parties se trouvant dans les différentes sessions accessibes depuis le répertoire de travail.");

				System.out.println("HELP : Cet écran");
				return;
			}
		}
		/*
		 * Paramètres PROFONDEUR_MAX, EVALUEUR, COUPS_ALEATOIRE Joueurs : [Humain|CPU] Nb manches Repertoire de sortie
		 * 
		 * 
		 * Commandes LISTER_COUPS
		 * 
		 * Syntaxe: -MAX_DEPTH_[ROUGE|BLEU] value -BRAIN_[ROUGE|BLEU] class -RANDOM_MOVES_[ROUGE|BLEU] value -VUE_[ROUGE|BLEU] ou LISTER_COUPS
		 */

		if (GO.contains("LISTER_COUPS"))
		{
			AdapteurMesures.main(args);
			return;
		}

		repertoire_session = getRepertoireSession();

		repertoire_session.mkdirs();

		File fichier_tournoi = new File(repertoire_session, "tournoi.txt");

		if (fichier_tournoi.exists())
		{
			System.err.format("Le fichier '%s' existe déjà.", fichier_tournoi.getAbsolutePath());
			return;
		}

		PrintStream sortie = null;
		try
		{
			sortie = new PrintStream(fichier_tournoi);
		}
		catch (FileNotFoundException e)
		{
			sortie = System.out;
			sortie.println("Erreur de création du fichier de sortie, sortie standard utilisée.");
			return;
		}

		System.out.println(String.format("Répertoire de sortie : '%s'", repertoire_session.getAbsolutePath()));

		IJoueur bleu = null;
		IJoueur rouge = null;

		bleu = choix_joueur("Bleu", ai_bleu, profondeur_max_bleu, evalueur_bleu, coups_aleatoires_bleu, timeout_uct_bleu);
		rouge = choix_joueur("Rouge", ai_rouge, profondeur_max_rouge, evalueur_rouge, coups_aleatoires_rouge, timeout_uct_rouge);

		if ( !GO.contains("GAMES"))
		{
			System.out.println("Nombre de manches (défaut 1) : ");

			try
			{
				nb_manches = Integer.parseInt(FromKeyboard.readLine());
			}
			catch (Exception e)
			{
				nb_manches = 1;
				System.out.println("Erreur de saisie, nombre de manches <- 1.");
			}
		}

		System.out.println(String.format("Nombre de manches : %d", nb_manches));

		// Jeu
		// TODO: Remplacer i_NeutronUI par une valeur valable (pas null), implémenter NeutronUI en CONSOLE.
		INeutronUI silentUI = new INeutronUI()
		{
			
			@Override
			public void setPlateau(Plateau iPlateau)
			{
				// TODO Auto-generated method stub
				
			}
			
			@Override
			public void OnJeuFini(eCase iVainqueur)
			{
				// TODO Auto-generated method stub
				
			}
		};
		
		Jeu jeu = new Jeu(silentUI, rouge, bleu, sortie);
		// Compteur de manche (indique la manche courante)
		int cpt_manche = 1;
		// Compte les victoires rouges
		int cpt_victoire_rouge = 0;
		// Pourcentage de victoire des rouges
		float pourcentage_victoire_rouge = 0;
		// N° de la manche ayant eu le maximum de coups
		int id_partie_max_coups = 0;
		// Nb de coups de la manche en ayant le max
		int max_coups = 0;
		// Nb de coups de la manche courante
		int nb_coups = 0;
		// Nb de coups total de toute les manches
		int nb_coups_total = 0;
		// Nb de coups en moyenne par partie
		float moyenne_coups = 0;
		// Vainqueur de la manche courante
		eCase vainqueur = eCase.VIDE;
		// Nb de parties nulles
		int nb_parties_nulles = 0;

		StringBuffer stats_buffer = new StringBuffer(300);
		tournoi_ouvert = true;

		while (cpt_manche <= nb_manches)
		{
			System.out.format("Manche %d / %d\r\n", cpt_manche, nb_manches);
			sortie.format("Début partie n°%d\r\n", cpt_manche);

			jeu.Redemarrer();
			
			while(jeu.getVainqueur() == eCase.VIDE)
			{
				try
				{
					Thread.sleep(5000);
				}
				catch(InterruptedException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			vainqueur = jeu.getVainqueur();
			if (vainqueur == eCase.NEUTRON)
			{
				nb_parties_nulles++ ;
				continue;
			}

			try
			{
				jeu.sauverHistorique(String.format("%s" + File.separator + "%s%d.txt", repertoire_session.getAbsolutePath(), PARTIE_PREFIX, cpt_manche), VUE);
			}
			catch (FileNotFoundException e)
			{
				sortie.println("Erreur sauvegarde historique : \"" + e.getLocalizedMessage() + "\"");
			}
			catch (DrawGameException e)
			{
				sortie.println("Erreur : partie nulle");
				nb_parties_nulles++ ;
				continue;
			}

			if (vainqueur == eCase.ROUGE)
			{
				cpt_victoire_rouge++ ;
			}
			pourcentage_victoire_rouge = ((float) cpt_victoire_rouge / (float) (cpt_manche)) * 100;

			nb_coups = jeu.getNbPlateaux();
			if (nb_coups > max_coups)
			{
				id_partie_max_coups = cpt_manche;
				max_coups = nb_coups;
			}
			nb_coups_total += nb_coups;
			moyenne_coups = ((float) nb_coups_total / (float) cpt_manche);

			sortie.format("Vainqueur de la partie n°%d : %s\r\n", cpt_manche, vainqueur);

			// Toutes les 10 manches, on affiche les stats
			if (((cpt_manche % 10) == 0) || (cpt_manche == nb_manches))
			{
				stats_buffer.setLength(0);
				stats_buffer.append("\r\n");
				stats_buffer.append("====================\r\n");
				stats_buffer.append(String.format("Rouge '%s(%s)' VS Bleu '%s(%s)'\r\n", rouge.getClass().getSimpleName(), evalueur_rouge.getClass().getSimpleName(), bleu.getClass().getSimpleName(), evalueur_bleu.getClass().getSimpleName()));
				stats_buffer.append(String.format("Nb parties : %d \t Nb victoires rouges : %d \t %% victoires rouges : %03.2f \t Nuls : %d \r\n", cpt_manche, cpt_victoire_rouge, pourcentage_victoire_rouge, nb_parties_nulles));
				stats_buffer.append(String.format("Max coups : %d (partie n°%d) \t Nb coups en moy : %03.2f\r\n", max_coups, id_partie_max_coups, moyenne_coups));
				stats_buffer.append("====================\r\n");

				System.out.println(stats_buffer);
				sortie.println(stats_buffer);
			}

			cpt_manche++ ;
			sortie.println();
			sortie.flush();
		}

		System.out.println("Tournoi fini, extraction des mesures..");
		AdapteurMesures.main(args);
	}
}
